﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Dokan;

namespace RamDrive
{
	public class RamDriveHost : DokanOperations
	{
		public static void Host(string sourceFile)
		{
			if (File.Exists(sourceFile) == false)
			{
				throw new FileNotFoundException();
			}

			var host = new RamDriveHost(sourceFile);

			var gate = new ManualResetEvent(false);

			var hostThread = new Thread(new ThreadStart(delegate
			{
				var opt = new DokanOptions
				{
					MountPoint = @"Z:\",
					DebugMode = false,
					UseStdErr = false,
					VolumeLabel = "RamDrive",
				};

				// Blocks the current thread, unless there was an error.
				int status = DokanNet.DokanMain(opt, host);

				gate.Set();

				switch (status)
				{
					case DokanNet.DOKAN_DRIVE_LETTER_ERROR:
						//Console.WriteLine("Drvie letter error");
						break;
					case DokanNet.DOKAN_DRIVER_INSTALL_ERROR:
						//Console.WriteLine("Driver install error");
						break;
					case DokanNet.DOKAN_MOUNT_ERROR:
						//Console.WriteLine("Mount error");
						break;
					case DokanNet.DOKAN_START_ERROR:
						//Console.WriteLine("Start error");
						break;
					case DokanNet.DOKAN_ERROR:
						//Console.WriteLine("Unknown error");
						break;
					case DokanNet.DOKAN_SUCCESS:
						//Console.WriteLine("Success");
						break;
					default:
						//Console.WriteLine("Unknown status: {0}", status);
						break;
				}
			}))
			{
				IsBackground = true,
			};

			hostThread.Start();

			do
			{
				if (Environment.GetLogicalDrives().Any(item => item == @"Z:\"))
				{
					//Console.WriteLine("Directory created!!!");

					break;
				}
			}
			while (gate.WaitOne(100) == false);
		}

		private RamDriveHost(string sourceFile)
		{
			_sourceFile = sourceFile;
			_sourceFileInfo = new FileInfo(_sourceFile);
			_fileName = Path.GetFileName(sourceFile);
			_fileLength = _sourceFileInfo.Length;
			_targetFilePath = @"\" + _fileName;

			_stream = new MemoryStream((int)_fileLength);
			_stream.Write(File.ReadAllBytes(_sourceFile), 0, (int)_fileLength);
			//using (var sourceStream = File.OpenRead(_sourceFile))
			//{
			//	_stream = new HighMemoryStream(sourceStream);
			//}

			_createdTime = _lastAccessTime = _lastWriteTime = DateTime.Now;
		}

		private string _sourceFile;
		private FileInfo _sourceFileInfo;
		private string _fileName;
		private long _fileLength;
		private string _targetFilePath;
		private object _syncLock = new object();
		private MemoryStream _stream;
		//private HighMemoryStream _stream;
		private DateTime _createdTime;
		private DateTime _lastAccessTime;
		private DateTime _lastWriteTime;

		#region Empty Methods

		public int Cleanup(string filename, DokanFileInfo info)
		{
			////Console.WriteLine("Cleanup : {0}", new { filename });

			return 0;
		}

		public int CloseFile(string filename, DokanFileInfo info)
		{
			////Console.WriteLine("CloseFile : {0}", new { filename });

			return 0;
		}

		public int DeleteDirectory(string filename, DokanFileInfo info)
		{
			////Console.WriteLine("DeleteDirectory : {0}", new { filename });

			return -1;
		}

		public int DeleteFile(string filename, DokanFileInfo info)
		{
			////Console.WriteLine("DeleteFile : {0}", new { filename });

			return -1;
		}

		public int FlushFileBuffers(string filename, DokanFileInfo info)
		{
			////Console.WriteLine("FlushFileBuffers : {0}", new { filename });

			return 0;
		}

		public int LockFile(string filename, long offset, long length, DokanFileInfo info)
		{
			////Console.WriteLine("LockFile : {0}", new { filename });

			return 0;
		}

		public int MoveFile(string filename, string newname, bool replace, DokanFileInfo info)
		{
			////Console.WriteLine("MoveFile : {0}", new { filename });

			return -1;
		}

		public int SetAllocationSize(string filename, long length, DokanFileInfo info)
		{
			////Console.WriteLine("SetAllocationSize : {0}", new { filename });

			return -1;
		}

		public int SetEndOfFile(string filename, long length, DokanFileInfo info)
		{
			////Console.WriteLine("SetEndOfFile : {0}", new { filename });

			return -1;
		}

		public int SetFileAttributes(string filename, System.IO.FileAttributes attr, DokanFileInfo info)
		{
			////Console.WriteLine("SetFileAttributes : {0}", new { filename });

			return 0;
		}

		public int SetFileTime(string filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info)
		{
			////Console.WriteLine("SetFileTime : {0}", new { filename });

			return 0;
		}

		public int UnlockFile(string filename, long offset, long length, DokanFileInfo info)
		{
			////Console.WriteLine("UnlockFile : {0}", new { filename });

			return 0;
		}

		public int Unmount(DokanFileInfo info)
		{
			////Console.WriteLine("Unmount : {0}");

			return 0;
		}

		#endregion Empty Methods

		public int CreateDirectory(string filename, DokanFileInfo info)
		{
			//Console.WriteLine("CreateDirectory : {0}", new { filename });

			if (filename == @"\")
			{
				info.IsDirectory = true;

				return 0;
			}

			return -1;
		}

		public int CreateFile(string filename, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileMode mode, System.IO.FileOptions options, DokanFileInfo info)
		{
			//Console.WriteLine("CreateFile : {0}", new { filename, mode });

			switch (mode)
			{
				case FileMode.Open:
					{
						if (filename == @"\")
						{
							info.IsDirectory = true;

							return 0;
						}
						else if (filename == _targetFilePath)
						{
							return 0;
						}

						break;
					}

				case FileMode.OpenOrCreate:
					{
						if (filename == _targetFilePath)
						{
							return 0;
						}

						break;
					}
			}

			return -1;
		}

		public int FindFiles(string filename, System.Collections.ArrayList files, DokanFileInfo info)
		{
			if (filename == @"\")
			{
				files.Add(new FileInformation
				{
					Attributes = FileAttributes.Normal,
					CreationTime = _createdTime,
					FileName = _fileName,
					Length = _fileLength,
					LastAccessTime = _lastAccessTime,
					LastWriteTime = _lastWriteTime,
				});
			}

			//Console.WriteLine("FindFiles : {0}", new { filename });

			return 0;
		}

		public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info)
		{
			//Console.WriteLine("GetDiskFreeSpace : {0}", new { freeBytesAvailable, totalBytes, totalFreeBytes });

			freeBytesAvailable = 1024;// 0;
			totalBytes = 1024;// (ulong)_fileLength;
			totalFreeBytes = 1024;// (ulong)_fileLength;

			return 0;
		}

		public int GetFileInformation(string filename, FileInformation fileinfo, DokanFileInfo info)
		{
			//Console.WriteLine("GetFileInformation : {0}", new { filename });

			if (filename == @"\")
			{
				fileinfo.Attributes = FileAttributes.Directory;
				fileinfo.CreationTime = _createdTime;
				fileinfo.FileName = _fileName;
				fileinfo.LastAccessTime = _lastAccessTime;
				fileinfo.LastWriteTime = _lastWriteTime;
			}
			else if (filename == _targetFilePath)
			{
				fileinfo.Attributes = FileAttributes.Normal;
				fileinfo.CreationTime = _createdTime;
				fileinfo.FileName = _fileName;
				fileinfo.Length = _fileLength;
				fileinfo.LastAccessTime = _lastAccessTime;
				fileinfo.LastWriteTime = _lastWriteTime;

				return 0;
			}

			return 0;
		}

		public int OpenDirectory(string filename, DokanFileInfo info)
		{
			//Console.WriteLine("OpenDirectory : {0}", new { filename });

			if (filename == @"\")
			{
				info.IsDirectory = true;

				return 0;
			}

			return -DokanNet.ERROR_PATH_NOT_FOUND;
		}

		public int ReadFile(string filename, byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info)
		{
			//Console.WriteLine("ReadFile : {0}", new { filename, offset });

			lock (_syncLock)
			{
				_stream.Position = (int)offset;

				readBytes = (uint)_stream.Read(buffer, 0, buffer.Length);
			}

			_lastAccessTime = DateTime.Now;

			return 0;
		}

		public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info)
		{
			//Console.WriteLine("WriteFile : {0}", new { filename, offset });

			lock (_syncLock)
			{
				_stream.Position = (int)offset;

				_stream.Write(buffer, 0, buffer.Length);

				writtenBytes = (uint)buffer.Length;

				if (_stream.Position > _fileLength)
				{
					_fileLength = _stream.Position;
				}
			}

			_lastWriteTime = DateTime.Now;

			return 0;
		}
	}
}
